package jp.millibit.json
{
	import flash.external.ExternalInterface;


	public class Parser
	{
		include "tt.as";

		public function Parser()
		{
		}

		public function parse(json:String):Object
		{
			var tokenizer:Tokenizer = new Tokenizer(json),
				context:ParserContext = new RootContext(),
				token:Token;

			while (true)
			{
				token = tokenizer.next(context.tokenTypes);
				if (token === null)
				{
					break;
				}
				context = context.pushToken(token);
			}

			if (!context.root)
			{
				throw tokenizer.error("Invalid JSON format.");
			}

			return context.value;
		}
	}
}

import jp.millibit.json.Token;
import jp.millibit.json.Tokenizer;

function unescapeUnicode(value:String):String
{
	if (value === "\"\"")
	{
		return "";
	}

	value = value.substring(1, value.length - 1);
	if (value.indexOf("\\") === -1)
	{
		return value;
	}

	var len:uint = value.length,
		hadSlash:Boolean = false,
		inUnicode:Boolean = false,
		buf:String = "",
		unicode:String = "",
		i:int,
		ch:String;

	for (i = 0; i < len; i++)
	{
		ch = value.charAt(i);
		if (inUnicode)
		{
			buf += ch;
			if (buf.length === 4)
			{
				buf += String.fromCharCode(parseInt(unicode, 16));
				unicode = "";
				inUnicode = false;
				hadSlash = false;
			}
			continue;
		}

		if (hadSlash)
		{
			hadSlash = false;

			switch (ch)
			{
				case "\\":
					buf += "\\";
					break;
				case "'":
					buf += "'";
					break;
				case "\"":
					buf += "\"";
					break;
				case "r":
					buf += "\r";
					break;
				case "f":
					buf += "\f";
					break;
				case "t":
					buf += "\t";
					break;
				case "n":
					buf += "\n";
					break;
				case "b":
					buf += "\b";
					break;
				case "u":
					inUnicode = true;
					break;
				default:
					buf += ch;
					break;
			}
			continue;
		}
		else if (ch === "\\")
		{
			hadSlash = true;
			continue;
		}
		buf += ch;
	}

	if (hadSlash)
	{
		buf += "\\";
	}

	return buf;
}

class ParserContext
{
	include "tt.as";

	public var value:Object;

	public var tokenTypes:Array;

	public var parent:ParserContext;

	public var root:Boolean;

	public function ParserContext(parent:ParserContext)
	{
		this.parent = parent;
		this.root = false;
	}

	public function pushToken(token:Token):ParserContext
	{
		return null;
	}
}

class RootContext extends ParserContext
{

	private static const TOKEN_TYPES_0:Array = [BEGIN_OBJECT, BEGIN_ARRAY, FALSE, TRUE, NULL, STRING, NUMBER];

	public function RootContext()
	{
		super(null);
		tokenTypes = TOKEN_TYPES_0;
		this.root = true;
	}

	public override function pushToken(token:Token):ParserContext
	{
		var context:ParserContext;

		switch (token.type)
		{
			case BEGIN_OBJECT:
				context = new ObjectContext(this);
				value = context.value;
				return context;
			case BEGIN_ARRAY:
				context = new ArrayContext(this);
				value = context.value;
				return context;
			case FALSE:
				value = false;
				return this;
			case TRUE:
				value = true;
				return this;
			case NULL:
				value = null;
				return this;
			case STRING:
				value = unescapeUnicode(token.value);
				return this;
			case NUMBER:
				value = Number(token.value);
				return this;
		}

		return this;
	}
}

class ObjectContext extends ParserContext
{
	private static const NAME_AWARE:int = 0;

	private static const NAME_SEPARATOR_AWARE:int = 1;

	private static const VALUE_AWARE:int = 2;

	private static const VALUE_SEPARATOR_AWARE:int = 3;

	private static const TOKEN_TYPES_0:Array = [STRING, END_OBJECT];

	private static const TOKEN_TYPES_1:Array = [BEGIN_OBJECT, BEGIN_ARRAY, FALSE, TRUE, NULL, STRING, NUMBER];

	private static const TOKEN_TYPES_2:Array = [VALUE_SEPARATOR, END_OBJECT];

	private static const TOKEN_TYPES_3:Array = [STRING];

	private static const TOKEN_TYPES_4:Array = [NAME_SEPARATOR];

	private var _mode:int;

	private var _name:String;

	public function ObjectContext(parent:ParserContext)
	{
		super(parent);

		_mode = NAME_AWARE;
		value = {};
		tokenTypes = TOKEN_TYPES_0;
	}

	public override function pushToken(token:Token):ParserContext
	{
		if (_mode === NAME_AWARE)
		{
			if (token.type === END_OBJECT)
			{
				return parent;
			}
			_name = unescapeUnicode(token.value);
			_mode = NAME_SEPARATOR_AWARE;
			tokenTypes = TOKEN_TYPES_4;
		}
		else if (_mode === NAME_SEPARATOR_AWARE)
		{
			_mode = VALUE_AWARE;
			tokenTypes = TOKEN_TYPES_1;
		}
		else if (_mode === VALUE_SEPARATOR_AWARE)
		{
			if (token.type === END_OBJECT)
			{
				return parent;
			}

			_mode = NAME_AWARE;
			tokenTypes = TOKEN_TYPES_3;
		}
		else
		{
			var value:Object,
				context:ParserContext;

			switch (token.type)
			{
				case BEGIN_OBJECT:
					context = new ObjectContext(this);
					value = context.value;
					break;
				case BEGIN_ARRAY:
					context = new ArrayContext(this);
					value = context.value;
					break;
				case FALSE:
					value = false;
					context = this;
					break;
				case TRUE:
					value = true;
					context = this;
					break;
				case NULL:
					value = null;
					context = this;
					break;
				case STRING:
					value = unescapeUnicode(token.value);
					context = this;
					break;
				case NUMBER:
					value = Number(token.value);
					context = this;
					break;
			}

			this.value[_name] = value;
			_mode = VALUE_SEPARATOR_AWARE;
			tokenTypes = TOKEN_TYPES_2;

			return context;
		}

		return this;
	}
}

class ArrayContext extends ParserContext
{
	private static const TOKEN_TYPES_1:Array = [BEGIN_OBJECT, BEGIN_ARRAY, FALSE, TRUE, NULL, STRING, NUMBER, END_ARRAY];

	private static const TOKEN_TYPES_2:Array = [VALUE_SEPARATOR, END_ARRAY];

	private static const TOKEN_TYPES_3:Array = [BEGIN_OBJECT, BEGIN_ARRAY, FALSE, TRUE, NULL, STRING, NUMBER];

	private var _value:Array;

	private var _valueAware:Boolean;

	private var _index:int;

	public function ArrayContext(parent:ParserContext)
	{
		super(parent);

		value = _value = [];
		_valueAware = true;
		tokenTypes = TOKEN_TYPES_1;
		_index = 0;
	}

	public override function pushToken(token:Token):ParserContext
	{
		if (_valueAware)
		{
			var value:Object,
				context:ParserContext;

			switch (token.type)
			{
				case END_ARRAY:
					return parent;
				case BEGIN_OBJECT:
					context = new ObjectContext(this);
					value = context.value;
					break;
				case BEGIN_ARRAY:
					context = new ArrayContext(this);
					value = context.value;
					break;
				case FALSE:
					value = false;
					context = this;
					break;
				case TRUE:
					value = true;
					context = this;
					break;
				case NULL:
					value = null;
					context = this;
					break;
				case STRING:
					value = unescapeUnicode(token.value);
					context = this;
					break;
				case NUMBER:
					value = Number(token.value);
					context = this;
					break;
			}

			tokenTypes = TOKEN_TYPES_2;
			_valueAware = false;
			_value[_index++] = value;

			return context;
		}
		else
		{
			if (token.type === END_ARRAY)
			{
				return parent;
			}

			tokenTypes = TOKEN_TYPES_3;
			_valueAware = true;

			return this;
		}
	}
}
